﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Net;
using OAuth.Net.Common;
using OAuth.Net.Components;
using OAuth.Net.Consumer;
using Rodunu.Movies.Contracts;

namespace Rodunu.Movies.Services.NetFlix.DataProvider.NFService
{
    public class NetflixService
    {
        #region Properties
        private OAuthService _getService;
        private OAuthService _postService;
        private OAuthService _deleteService;
        private IToken _requestToken;
        private IToken _accessToken;
        private string _userID;
        private string _callbackUrl;
        private string _tokenKey;
        private UserInfo _userInfo;

        public IToken RequestToken
        {
            get { return _requestToken; }
            set { _requestToken = value; }
        }

        public IToken AccessToken {
            get { return _accessToken; }
            set { _accessToken = value; }
        }

        public string UserId {
            get { return _userID; }
            set { _userID = value; }
        }

        public string CallbackUrl {
            get { return _callbackUrl; }
            set { _callbackUrl = value; }
        }

        public Uri AuthenticationUri
        {
            get
            {
                NameValueCollection parameters = new NameValueCollection();
                parameters.Add("application_name", _getService.Consumer.FriendlyName);
                parameters.Add("oauth_consumer_key", _getService.Consumer.Key);
                if (!string.IsNullOrEmpty(_callbackUrl))
                    parameters.Add("oauth_callback", _callbackUrl);
                return _getService.BuildAuthorizationUrl(RequestToken, parameters);
            }
        }

        public Uri AuthorizationUri
        {
            get
            {
                return _getService.AuthorizationUrl;
            }
        }

        public string ApplicationName
        {
            get
            {
                return _getService.Consumer.FriendlyName;
            }
        }

        public string ConsumerKey
        {
            get
            {
                return _getService.Consumer.Key;
            }
        }

        private static readonly Configuration.NetflixServiceConfigurationSection Config = new Configuration.NetflixServiceConfigurationSection();  //(Configuration.NetflixServiceConfigurationSection)ConfigurationManager.GetSection("Netflix");
        private readonly ITokenStore _tokenStore = Config.GetTokenStore();
        #endregion

        #region Constructors

        public NetflixService(string tokenKey)
        {
            MakeServices(tokenKey);
        }

        public NetflixService()
        {
            MakeServices("");
        }

        public NetflixService(UserInfo userId)
        {
            _userInfo = userId;
            MakeServices("");
        }

        private void MakeServices(string tokenKey)
        {
            _getService = OAuthService.Create(
                new Uri("http://api.netflix.com/oauth/request_token"),
                new Uri("https://api-user.netflix.com/oauth/login"),
                new Uri("http://api.netflix.com/oauth/access_token"),
                "GET",
                false,
                string.Empty,
                "HMAC-SHA1",
                "1.0",
                new OAuthConsumer(Config.APIKey, Config.Secret, Config.ApplicationName));
            _postService = OAuthService.Create(
                new Uri("http://api.netflix.com/oauth/request_token"),
                new Uri("https://api-user.netflix.com/oauth/login"),
                new Uri("http://api.netflix.com/oauth/access_token"),
                "POST",
                false,
                string.Empty,
                "HMAC-SHA1",
                "1.0",
                new OAuthConsumer(Config.APIKey, Config.Secret, Config.ApplicationName));
            _deleteService = OAuthService.Create(
                new Uri("http://api.netflix.com/oauth/request_token"),
                new Uri("https://api-user.netflix.com/oauth/login"),
                new Uri("http://api.netflix.com/oauth/access_token"),
                "DELETE",
                false,
                string.Empty,
                "HMAC-SHA1",
                "1.0",
                new OAuthConsumer(Config.APIKey, Config.Secret, Config.ApplicationName));
            _tokenKey = tokenKey;
            if (_tokenStore != null)
            {
                GetRequestToken();
                FileTokenStore.TokenContainer tk = _tokenStore.GetTokens(_userInfo);
                if (tk != null)
                {
                    if (tk.RequestToken != null)
                    {
                        _requestToken = tk.RequestToken;
                    }

                    if (tk.AccessToken != null)
                    {
                        _accessToken = tk.AccessToken;
                    }

                    if (!string.IsNullOrEmpty(tk.UserID))
                    {
                        _userID = tk.UserID;
                    }
                }
                //CallbackUrl = "http://dmit14/Services/RNT.ashx";
                
            }
        }
        #endregion

        #region Public Methods

        public string GetGenericResource(string uri)
        {
            NameValueCollection parameters = new NameValueCollection();
            return new StreamReader(GetUnprotected(uri, parameters).GetResponseStream()).ReadToEnd();
        }

        public bool GetRequestToken()
        {
            try
            {
                OAuthRequest request = OAuthRequest.Create(new Uri(_getService.AuthorizationUrl.ToString()), _getService);
                OAuthResponse response = request.GetResource();

                RequestToken = response.Token;
                SaveTokens();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool GetAccessToken()
        {
            //try
            //{
                DoGetAccessToken();
                SaveTokens();
                return true;
            //}
            //catch
            //{
            //    return false;
            //}
        }

        #endregion

        #region Internal Methods

        internal OAuthResource GetUnprotected(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _getService,
                new OAuthToken(TokenType.Request, "", "", ""),
                new OAuthToken(TokenType.Access, "", "", ""));

            rq.OnBeforeGetProtectedResource += new EventHandler<PreProtectedResourceRequestEventArgs>((object sender, PreProtectedResourceRequestEventArgs args) =>
                                                                                                      args.AccessToken = null);
            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                throw new OAuthNotAuthorizedException();
            }
        }

        internal OAuthResource GetUserResource(string uri, NameValueCollection parameters)
        {
            if (UserId != null && UserId != string.Empty)
                return Get(uri.Replace("{userid}", UserId), parameters);
            else
                return Get(uri, parameters);
        }

        internal OAuthResource Get(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _getService,
                RequestToken,
                AccessToken);

            //rq.OnReceiveAccessToken += new EventHandler<AccessTokenReceivedEventArgs>(rq_OnReceiveAccessToken);
            //rq.Service.BuildAuthorizationUrl
            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                RequestToken = response.Token;
                SaveTokens();
                return null;
                //throw new OAuthNotAuthorizedException();
            }
        }

        internal OAuthResource Post(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _postService,
                RequestToken,
                AccessToken);

            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                RequestToken = response.Token;
                SaveTokens();

                throw new OAuthNotAuthorizedException();
            }
        }

        internal OAuthResource Delete(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _deleteService,
                RequestToken,
                AccessToken);
            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                RequestToken = response.Token;
                SaveTokens();

                throw new OAuthNotAuthorizedException();
            }
        }

        #endregion
        
        #region Private Methods
        
        void rq_OnReceiveAccessToken(object sender, AccessTokenReceivedEventArgs e)
        {
            AccessToken = e.AccessToken;
            UserId = e.AdditionalParameters["user_id"];
            SaveTokens();

            OAuthRequest req = sender as OAuthRequest;
            req.ResourceUri = new Uri(req.ResourceUri.OriginalString.Replace("{userid}", UserId));
        }

        void SaveTokens()
        {
            if (_tokenStore != null)
            {
                FileTokenStore.TokenContainer tk =_tokenStore.SaveTokens(_userInfo, _userID, _requestToken, _accessToken);
                _requestToken = tk.RequestToken;
            }
        }

        #endregion

        protected virtual void DoGetAccessToken()
        {
            // Fire the OnBeforeGetAccessToken event
            PreAccessTokenRequestEventArgs preArgs = new PreAccessTokenRequestEventArgs(
                this._getService.AccessTokenUrl,
                this._getService.HttpMethod,
                this.RequestToken);

            // Create and sign the request
            HttpWebRequest request = CreateAndSignRequest(
                preArgs.RequestUri,
                preArgs.HttpMethod,
                null,
                this.RequestToken);

            HttpWebResponse response = null;
            OAuthParameters responseParameters = null;

            // Get the service provider response
            try
            {
                response = (HttpWebResponse)request.GetResponse();

                // Parse the parameters and re-throw any OAuthRequestException from the service provider
                responseParameters = OAuthParameters.Parse(response);
                OAuthRequestException.TryRethrow(responseParameters);
            }
            catch (WebException e)
            {
                // Parse the parameters and re-throw any OAuthRequestException from the service provider
                responseParameters = OAuthParameters.Parse(e.Response as HttpWebResponse);
                OAuthRequestException.TryRethrow(responseParameters);

                // If no OAuthRequestException, rethrow the WebException
                throw;
            }

            // Store the access token
            this.AccessToken = new OAuthToken(
                TokenType.Access,
                responseParameters.Token,
                responseParameters.TokenSecret,
                this._getService.Consumer);
            AccessToken.Status = TokenStatus.Authorized;
            _userID = responseParameters.AdditionalParameters["user_id"];
        }

        protected virtual HttpWebRequest CreateAndSignRequest(Uri requestUri, string httpMethod, NameValueCollection additionalParameters, IToken token)
        {
            int timestamp = UnixTime.ToUnixTime(DateTime.Now);

            OAuthParameters authParameters = new OAuthParameters()
            {
                ConsumerKey = this._getService.Consumer.Key,
                Realm = this._getService.Realm,
                SignatureMethod = this._getService.SignatureMethod,
                Timestamp = timestamp.ToString(CultureInfo.InvariantCulture),
                Nonce = this._getService.ComponentLocator.GetInstance<INonceProvider>().GenerateNonce(timestamp),
                Version = this._getService.OAuthVersion
            };

            if (token != null)
                authParameters.Token = token.Token;

            if (additionalParameters != null && additionalParameters.Count > 0)
                authParameters.AdditionalParameters.Add(additionalParameters);

            // Normalize the request uri for signing
            if (!string.IsNullOrEmpty(requestUri.Query))
            {
                UriBuilder mutableRequestUri = new UriBuilder(requestUri);

                // TODO: Will the parameters necessarily be Rfc3698 encoded here? If not, then Rfc3968.SplitAndDecode will throw FormatException
                authParameters.AdditionalParameters.Add(Rfc3986.SplitAndDecode(mutableRequestUri.Query.Substring(1)));

                mutableRequestUri.Query = null;
                requestUri = mutableRequestUri.Uri;
            }

            // Check there is a signing provider for the signature method
            ISigningProvider signingProvider = this._getService.ComponentLocator.GetInstance<ISigningProvider>(Constants.SigningProviderIdPrefix + this._getService.SignatureMethod);

            if (signingProvider == null)
            {
                // There is no signing provider for this signature method
                OAuthRequestException.ThrowSignatureMethodRejected(null);
            }

            // Double check the signing provider declares that it can handle the signature method
            if (!signingProvider.SignatureMethod.Equals(this._getService.SignatureMethod))
                OAuthRequestException.ThrowSignatureMethodRejected(null);

            // Compute the signature
            authParameters.Signature = signingProvider.ComputeSignature(
                SignatureBase.Create(httpMethod, requestUri, authParameters),
                this._getService.Consumer.Secret,
                (token != null && token.Secret != null) ? token.Secret : null);

            if (this._getService.UseAuthorizationHeader)
            {
                // Put the OAuth parameters in the header and the additional parameters in the query string
                string authHeader = authParameters.ToHeaderFormat();
                string query = Rfc3986.EncodeAndJoin(authParameters.AdditionalParameters);

                if (!string.IsNullOrEmpty(query))
                {
                    UriBuilder mutableRequestUri = new UriBuilder(requestUri);
                    if (string.IsNullOrEmpty(mutableRequestUri.Query))
                        mutableRequestUri.Query = query;
                    else
                        mutableRequestUri.Query = mutableRequestUri.Query.Substring(1) + "&" + query;

                    requestUri = mutableRequestUri.Uri;
                }

                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
                request.Method = httpMethod;
                request.Headers.Add(HttpRequestHeader.Authorization, authHeader);
                return request;
            }
            else
            {
                string query = authParameters.ToQueryStringFormat();

                UriBuilder mutableRequestUri = new UriBuilder(requestUri);
                if (string.IsNullOrEmpty(mutableRequestUri.Query))
                    mutableRequestUri.Query = query;
                else
                    mutableRequestUri.Query = mutableRequestUri.Query.Substring(1) + "&" + query;

                requestUri = mutableRequestUri.Uri;

                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
                request.Method = httpMethod;
                return request;
            }
        }

    }

    public class OAuthNotAuthorizedException : Exception { }

    public class ResourceProtectedException : Exception { }
}